home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / DynaDoodle / Manager.m < prev    next >
Text File  |  1993-12-15  |  16KB  |  392 lines

  1. /**------------------------------------*------------------------------------**
  2.     RELEASED TO THE PUBLIC DOMAIN BY CODEWORKS DEVELOPMENT, DECEMBER 1993.
  3.     You may freely copy, distribute and reuse the code in this example. Codeworks disclaims any warranty of any kind, expressed or implied, as to its fitness for any particular use.
  4.     Author:  Andrew Vyrros, av@codeworks.com
  5.  **------------------------------------*------------------------------------**/
  6. /**------------------------------------*------------------------------------**
  7.     File:    Manager.m
  8.     Project: DynaDoodle
  9.     Info:    Implementation of the Manager class. Manager acts as the central object for the DynaDoodle application, and is NXApp's delegate.  It coordinates the loading of doodle modules and the selection of the current doodle. It also forwards some graphical control methods to the current doodle.
  10.     In its implementation, Manager is tied explicitly to DoodleModule and Doodle. It would have been possible to create a version with far less dependence on these classes. Such a version would have made it easier to use Manager in other projects, but I felt that this would have obscured the concepts and constructs of dynamic loading. I hope that once you understand the basic structure, you can easily adapt it to your own uses.
  11.  **------------------------------------*------------------------------------**/
  12.  
  13. #import "Manager.h"
  14. #import "DoodleBundle.h"
  15. #import "Doodle.h"
  16. #import <appkit/appkit.h>
  17. #import <sys/dir.h>
  18.  
  19.  
  20. /* Subdirectory in Library directory to search for Doodle modules. */
  21. #define LIBRARYSUBPATH           "DynaDoodle"
  22. /*
  23.  * Extension for Doodle modules. The default extension for Project Builder
  24.  * modules is .bundle. If you want to use the more appropriate .doodle, you
  25.  * can switch it here. But then you will need to change the names of the
  26.  * module packages, too. In NEXTSTEP 3.2, you can do this directly from PB.
  27.  * In 3.1 or 3.0, there is no UI to do this in PB, and the Makefile rules
  28.  * for BUNDLE_EXTENSION don't work right either, so you will either need to
  29.  * come up with your own Makefile hacks, or change the extensions by hand.
  30.  * For maximum compatibility, I have left it set to the default .bundle.
  31. */
  32. #define MODULEEXTENSION          "bundle"
  33. //#define MODULEEXTENSION          "doodle"
  34.  
  35.  
  36. @implementation Manager
  37.  
  38.  
  39. /**------------------------------------*------------------------------------**
  40.     Method:  appDidInit:
  41.     Info:    Applicaton delegate method, informs Manager that NXApp has been initialized. Manager uses this opportunity to create the Bundles, fill a PopUpList with the available Doodles, and set the current Doodle to a default setting.
  42.     Return:  (id)self
  43.  **------------------------------------*------------------------------------**/
  44. - appDidInit:sender
  45. {
  46.   /*
  47.    * Create List of Bundles. This will only create the Bundle objects, it
  48.    * will not load any modules or create any objects within the modules. By
  49.    * changing the argument to YES, it will load the modules, too. This way
  50.    * you can see the performance tradeoff of loading modules as needed vs.
  51.    * loading all modules at launch time. 
  52.    */
  53.   bundleList = [[List allocFromZone:[self zone]] init];
  54.   [self createBundlesAndLoadModules:NO];
  55. //  [self createBundlesAndLoadModules:YES];
  56.   
  57.   /*
  58.    * Fill currentDoodlePopUpList with available modules, then set current
  59.    * doodle to default. 
  60.    */
  61.   [self fillCurrentDoodlePopUpList];
  62.   [self setCurrentDoodleToDefault];
  63.   
  64.   /*
  65.    * Because Interface Builder does not allow you to set the target of a
  66.    * PopUpList, do it programmatically now. 
  67.    */
  68.   [[[currentDoodleButton target] setTarget:self]
  69.    setAction:@selector(takeCurrentDoodleFrom:)];
  70.   
  71.   /* Since the main Window was deferred, present it now. */
  72.   [[doodleBox window] makeKeyAndOrderFront:self];
  73.   
  74.   return self;
  75. }
  76.  
  77.  
  78. /**------------------------------------*------------------------------------**
  79.     Method:  free
  80.     Info:    Frees all resources allocated by Manager.
  81.     Return:  (id)nil
  82.  **------------------------------------*------------------------------------**/
  83. - free
  84. {
  85.   /*
  86.    * Just to be safe, remove the currentDoodle from the View hierarchy first,
  87.    * then free everything. 
  88.    */
  89.   [currentDoodle removeFromSuperview];
  90.   bundleList = [[bundleList freeObjects] free];
  91.   return [super free];
  92. }
  93.  
  94.  
  95. /**------------------------------------*------------------------------------**
  96.     Method:  createBundlesAndLoadModules:
  97.     Info:    Creates full set of Bundles and adds to bundleList. Searches for modules with extension MODULEEXTENSION. Looks in LIBRARYSUBPATH within Library directories and then app package. If doLoad = YES, will load modules too. Otherwise, just creates Bundles, defers loading of modules until each is needed.
  98.     Return:  (id)self
  99.  **------------------------------------*------------------------------------**/
  100. - createBundlesAndLoadModules:(BOOL)doLoad
  101. {
  102.   char                libraryDir[MAXPATHLEN + 1];
  103.   
  104.   /* Just to be safe, clear out bundleList. */
  105.   [bundleList freeObjects];
  106.   
  107.   /* Create bundles for ~/Library/LIBRARYSUBPATH. */
  108.   strcpy(libraryDir, NXHomeDirectory());
  109.   strcat(libraryDir, "/Library/");
  110.   strcat(libraryDir, LIBRARYSUBPATH);
  111.   [self createBundlesForDirectory:libraryDir loadModules:doLoad];
  112.   
  113.   /* Create bundles for /LocalLibrary/LIBRARYSUBPATH. */
  114.   strcpy(libraryDir, "/LocalLibrary/");
  115.   strcat(libraryDir, LIBRARYSUBPATH);
  116.   [self createBundlesForDirectory:libraryDir loadModules:doLoad];
  117.  
  118.   /* Create bundles for /NextLibrary/LIBRARYSUBPATH. */
  119.   strcpy(libraryDir, "/NextLibrary/");
  120.   strcat(libraryDir, LIBRARYSUBPATH);
  121.   [self createBundlesForDirectory:libraryDir loadModules:doLoad];
  122.  
  123.   /* Finally, create bundles for app package. */
  124.   strcpy(libraryDir, [[NXBundle mainBundle] directory]);
  125.   [self createBundlesForDirectory:libraryDir loadModules:doLoad];
  126.     
  127.   return self;
  128. }
  129.   
  130.   
  131. /**------------------------------------*------------------------------------**
  132.     Function:fileNameHasExtension
  133.     Info:    Helper function, determines whether a file name string has the given extension. Used by createBundlesForDirectory:loadModules:.
  134.     Return:  (BOOL)y/n
  135.  **------------------------------------*------------------------------------**/
  136. static BOOL fileNameHasExtension(const char *f, const char *e)
  137. {
  138.   const char         *dot;
  139.  
  140.   if (f && e && (dot = strrchr(f, '.')) && !strcmp(dot + 1, e))
  141.     return YES;
  142.   else
  143.     return NO;
  144. }
  145.  
  146.  
  147. /**------------------------------------*------------------------------------**
  148.     Method:  createBundlesForDirectory:loadModules:
  149.     Info:    Creates set of Bundles for modules in given directory. If doLoad = YES, will load modules too. Otherwise, just creates Bundles, defers loading of modules until each is needed.
  150.     Note that Bundles are added to the List in the order that their module packages are found by readdir(), which can be unpredictable. Depending on your needs, you may want to adjust this order now. See fillCurrentDoodlePopUpList below.
  151.     Return:  (id)self
  152.  **------------------------------------*------------------------------------**/
  153. - createBundlesForDirectory:(const char *)dirPath loadModules:(BOOL)doLoad
  154. {
  155.   char                modulePath[MAXPATHLEN + 1];
  156.   char               *modulePathInsert;
  157.   DIR                *dir;
  158.   struct direct      *dirEntry;
  159.   DoodleBundle       *newDoodleBundle;
  160.  
  161.   /* Copy dirPath to modulePath, append '/', get set to append module name. */
  162.   strcpy(modulePath, dirPath);
  163.   modulePathInsert = modulePath + strlen(modulePath);
  164.   *modulePathInsert = '/';
  165.   modulePathInsert++;
  166.   
  167.   /* Get a DIR for dirPath. */
  168.   dir = opendir(dirPath);
  169.   if (!dir)
  170.     return self;
  171.  
  172.   /* Loop through entries in dir. */
  173.   while (dirEntry = readdir(dir))
  174.   {
  175.     /* If entry name starts with '.' or does not have right extension, skip. */
  176.     if ((dirEntry->d_name[0] == '.')
  177.         || !fileNameHasExtension(dirEntry->d_name, MODULEEXTENSION))
  178.       continue;
  179.  
  180.     /* Copy module name to correct point in modulePath. */
  181.     strcpy(modulePathInsert, dirEntry->d_name);
  182.     
  183.     /* Create new DoodleBundle, add to bundleList. */
  184.     newDoodleBundle = [[DoodleBundle allocFromZone:[self zone]]
  185.                        initForDirectory:modulePath];
  186.     [bundleList addObject:newDoodleBundle];
  187.  
  188.     /* If asked to load now, force new DoodleBundle to load module. */
  189.     if (doLoad)
  190.       [newDoodleBundle doodle];
  191.   }
  192.   
  193.   /* Clean up after opendir(). */
  194.   closedir(dir);
  195.  
  196.   return self;
  197. }
  198.  
  199.  
  200. /**------------------------------------*------------------------------------**
  201.     Method:  fillCurrentDoodlePopUpList
  202.     Info:    Fills a PopUpList with the names of the available Doodles. Also sets tags of entries for easy identification.
  203.     In a real program, you might want to alphasort the list of module names before putting them into the PopUpList. Even better is to give the user some way to set the order that the modules are listed. This makes the most sense when the selection control is something like a Matrix of ButtonCells. Another bonus option is to let the user add and subtract modules that the program presents. See createBundlesForDirectory:loadModules: above.
  204.     Return:  (id)self
  205.  **------------------------------------*------------------------------------**/
  206. - fillCurrentDoodlePopUpList
  207. {
  208.   PopUpList          *currentDoodlePopUpList;
  209.   int                 i,
  210.                       numBundles;
  211.   const char         *doodleName;
  212.  
  213.   /* Get the PopUpList from its cover Button. */
  214.   currentDoodlePopUpList = [currentDoodleButton target];
  215.  
  216.   /* Empty out the PopUpList. */
  217.   i = [currentDoodlePopUpList count];
  218.   while (i--)
  219.     [[currentDoodlePopUpList removeItemAt:i] free];
  220.  
  221.   /* Step through the Bundles, adding each doodleName to the PopUpList. */
  222.   numBundles = [bundleList count];
  223.   for (i = 0; i < numBundles; i++)
  224.   {
  225.     /*
  226.      * Note that we ask the DoodleBundle for the doodleName, rather than the
  227.      * Doodle itself. If we used Doodle to get the doodleName, we would have
  228.      * to instantiate the Doodle now, which would require loading the module
  229.      * now. This would preclude the lazy loading of modules. So DoodleBundle
  230.      * provides the doodleName instead. 
  231.      */
  232.     doodleName = [[bundleList objectAt:i] doodleName];
  233.     [[currentDoodlePopUpList addItem:doodleName] setTag:i];
  234.   }
  235.  
  236.   return self;
  237. }
  238.  
  239.  
  240. /**------------------------------------*------------------------------------**
  241.     Method:  setCurrentDoodleToDefault
  242.     Info:    Sets the current Doodle to a default setting when the program launches. Also synchronizes the PopUpList with this choice.
  243.     Taking the easy way out, this just gets the Doodle at index 0 in bundleList. A real program would allow the user to set the preferred module to appear at launch time.
  244.     Return:  (id)self
  245.  **------------------------------------*------------------------------------**/
  246. - setCurrentDoodleToDefault
  247. {
  248.   int                 defaultIndex;
  249.   Doodle             *defaultDoodle;
  250.   PopUpList          *currentDoodlePopUpList;
  251.  
  252.   /* Get Doodle from DoodleBundle at index 0. */
  253.   defaultIndex = 0;
  254.   defaultDoodle = [[bundleList objectAt:defaultIndex] doodle];
  255.   [self setCurrentDoodle:defaultDoodle];
  256.  
  257.   /* Synchronize PopUpList. */
  258.   currentDoodlePopUpList = [currentDoodleButton target];
  259.   [[currentDoodlePopUpList itemList] selectCellWithTag:defaultIndex];
  260.   [currentDoodleButton setTitle:[currentDoodlePopUpList selectedItem]];
  261.  
  262.   return self;
  263. }
  264.  
  265.  
  266. /**------------------------------------*------------------------------------**
  267.     Method:  setCurrentDoodle:
  268.     Info:    Sets currentDoodle to the supplied Doodle. To do this, it sets the foreground and background colors of the Doodle to the current settings, then puts it into the doodleBox.
  269.     Return:  (id)self
  270.  **------------------------------------*------------------------------------**/
  271. - setCurrentDoodle:(Doodle *)newDoodle
  272. {
  273.   DoodleBundle       *currentDoodleBundle;
  274.  
  275.   /* If this already is currentDoodle, don't do extra work. */
  276.   if (newDoodle == currentDoodle)
  277.     return self;
  278.   
  279.   /* Update currentDoodle, get ready to muck with Views. */
  280.   currentDoodle = newDoodle;
  281.   [[doodleBox window] disableDisplay];
  282.  
  283.   /* First make sure that currentDoodle has correct color settings. */
  284.   [currentDoodle setForegroundColor:[foregroundColorWell color]];
  285.   [currentDoodle setBackgroundColor:[backgroundColorWell color]];
  286.  
  287.   /*
  288.    * Plop currentDoodle into doodleBox. Using setContentView: is not
  289.    * necessarily the smartest way to do it, but it's quick and easy.
  290.    * Unfortunately, this will leak the default contentView that is created
  291.    * when a Box is initialized. This can be avoided, of course, but it's not
  292.    * worth it for this demo. 
  293.    */
  294.   [doodleBox setContentView:currentDoodle];
  295.   
  296.   /* Update descriptionTextField with the new Doodle's description. */
  297.   currentDoodleBundle = [DoodleBundle bundleForClass:[currentDoodle class]];
  298.   [descriptionTextField setStringValue:[currentDoodleBundle doodleDescription]];
  299.   
  300.   /*
  301.    * Plop the new Doodle's customControlView into customControlBox. The same
  302.    * caveat mentioned above still applies. In addition, you should note that
  303.    * setContentView: will resize the supplied View to be the same size as the
  304.    * old contentView, which can screw up your carefully layed out controls.
  305.    * In this project, all the customControlViews exactly fit into
  306.    * customControlBox, so it's not an issue. But a more robust solution would
  307.    * probably center the customControlView in customControlBox. 
  308.    */
  309.   [customControlBox setContentView:[currentDoodle customControlView]];
  310.   
  311.   /* Clean up Views. */
  312.   [[currentDoodle customControlView] update];
  313.   [[doodleBox window] reenableDisplay];
  314.   [[doodleBox window] displayIfNeeded];
  315.  
  316.   return self;
  317. }
  318.   
  319.  
  320. /**------------------------------------*------------------------------------**
  321.     Method:  takeCurrentDoodleFrom:
  322.     Info:    Action method, called by PopUpList of Doodles. Sets currentDoodle based on tag of sender, using setCurrentDoodle: to do the work.
  323.     Return:  (id)self
  324.  **------------------------------------*------------------------------------**/
  325. - takeCurrentDoodleFrom:sender
  326. {
  327.   Doodle             *newDoodle;
  328.  
  329.   newDoodle = [[bundleList objectAt:[sender selectedTag]] doodle];
  330.   [self setCurrentDoodle:newDoodle];
  331.  
  332.   return self;
  333. }
  334.  
  335.  
  336. /**------------------------------------*------------------------------------**
  337.     Method:  takeForegroundColorFrom:, takeBackgroundColorFrom:
  338.     Info:    Action methods, called by foregroundColorWell/backgroundColorWell. Set colors of currentDoodle based on value of sender.
  339.     Return:  (id)self
  340.  **------------------------------------*------------------------------------**/
  341. - takeForegroundColorFrom:sender
  342. {
  343.   [currentDoodle setForegroundColor:[sender color]];
  344.  
  345.   return self;
  346. }
  347.  
  348.  
  349. - takeBackgroundColorFrom:sender
  350. {
  351.   [currentDoodle setBackgroundColor:[sender color]];
  352.  
  353.   return self;
  354. }
  355.  
  356.  
  357. /**------------------------------------*------------------------------------**
  358.     Method:  showInfoPanel:
  359.     Info:    Action method, called by Info... MenuCell. Checks if nib is loaded, then pops up Panel.
  360.     Return:  (id)self, nil if there is a problem.
  361.  **------------------------------------*------------------------------------**/
  362. - showInfoPanel:sender
  363. {
  364.   char                nibPath[MAXPATHLEN + 1];
  365.  
  366.   /* If infoPanel has not been loaded yet, load it. */
  367.   if (!infoPanel)
  368.   {
  369.     /*
  370.      * Note that we go to the extra trouble to get the Bundle for this class,
  371.      * rather than just looking in the main Bundle with loadNibSection....
  372.      * This is good defensive programming in case this class is moved into
  373.      * its own dynamic module some day--the code will still work perfectly! 
  374.      */
  375.     [[NXBundle bundleForClass:[self class]]
  376.      getPath:nibPath forResource:"Info" ofType:"nib"];
  377.     [NXApp loadNibFile:nibPath owner:self withNames:NO fromZone:[self zone]];
  378.   }
  379.  
  380.   /* If infoPanel loaded OK, send it out, otherwise, return nil. */
  381.   if (infoPanel)
  382.   {
  383.     [infoPanel makeKeyAndOrderFront:self];
  384.     return self;
  385.   }
  386.   else
  387.     return nil;
  388. }
  389.       
  390.  
  391. @end
  392.